#ifndef __M_HELPER_H__
#define __M_HELPER_H__

#include <sqlite3.h>
#include <string>
#include <iostream>
#include <vector>
#include <random>
#include <sstream>
#include <iomanip>
#include <atomic>
#include <sys/stat.h>
#include <fstream>
#include <cstring>
#include "mq_logger.hpp"

namespace mymq
{
    class SqliteHelper
    {
    public:
        /*int (*callback)(void*,int,char**,char**)
            void* : 是设置的在回调时传入的 arg 参数
            int：一行中数据的列数
            char**：存储一行数据的字符指针数组
            char**：每一列的字段名称
            这个回调函数有个 int 返回值，成功处理的情况下必须返回 0，返回非 0
            会触发 ABORT 退出程序
        */
        typedef int(*SqliteCallback)(void*, int, char**, char**);
        SqliteHelper(const std::string& filename)
        : _dbfile(filename),
        _handler(nullptr)
        {}

        bool open(int safe_level = SQLITE_OPEN_FULLMUTEX)
        {
            // int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs )
            int ret = sqlite3_open_v2(_dbfile.c_str(), &_handler, 
                SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_level, nullptr);
            if (ret != SQLITE_OK)
            {
                ELOG("创建/打开sqlite3数据库失败: %s", sqlite3_errmsg(_handler));
                return false;
            }
            return true;
        }

        bool exec(const std::string& sql, SqliteCallback scb, void* arg)
        {
            // int sqlite3_exec(sqlite3*, char *sql, int(*callback)(void*,int,char**,char**), 
            //                  void* arg, char **err)
            int ret = sqlite3_exec(_handler, sql.c_str(), scb, arg, nullptr);
            if (ret != SQLITE_OK)
            {
                std::cout << sql << std::endl;
                ELOG("%s\n执行 sql 语句失败: %s", sql.c_str(), sqlite3_errmsg(_handler));
                return false;
            }
            return true;
        }

        void close()
        {
            if (_handler) sqlite3_close_v2(_handler);
        }
    private:
        std::string _dbfile;
        sqlite3* _handler;
    };

    class StrHelper
    {
    public:
        static size_t split(const std::string& str, const std::string& sep, std::vector<std::string>& result)
        {
            // news...music.#.pop
            int pos = 0; // 查找结果
            int idx = 0; // 开始查找的位置
            while(idx < str.size())
            {
                pos = str.find(sep, idx);
                if (pos == std::string::npos)
                {
                    // 没找到，直接从开始查找位置开始分割，一直到末尾
                    result.push_back(str.substr(idx));
                    return result.size();
                }
                // 找到
                if (pos == idx)
                {
                    // 说明开始位置就是分隔符，跳过
                    idx = pos + sep.size();
                    continue;
                }
                result.push_back(str.substr(idx, pos - idx));
                idx = pos + sep.size();
            }
            return result.size();
        }
    };

    class UUIDHelper
    {
    public:
        static std::string uuid()
        {
            // 1.生成8个 0~255 的随机数
            std::random_device rd; // 使用机器码生成随机数，效率低。所以只作为种子使用
            std::mt19937_64 generator(rd()); // 梅森旋转算法生成随机数
            std::uniform_int_distribution<int> distribution(0, 255); // 在此范围取值
            
            std::stringstream ss;
            for (int i = 0; i < 8; i++)
            {
                ss << std::setw(2) << std::setfill('0') << std::hex << distribution(generator);
                // 8-4-4-
                if (i == 3 || i == 5 || i == 7)
                    ss << '-';
            }
        
            // 2.生成一个 8 字节序号
            static std::atomic<size_t> seq(1);
            size_t num = seq.fetch_add(1); // seq为静态，每次生成uuid后都会自增1
        
            // 取下每一字节，转为16进制
            for (int i = 7; i >= 0; i--)
            {
                ss << std::setw(2) << std::setfill('0') << std::hex << ((num >> i * 8) & 0xff);
                // 4-12
                if (i == 6)
                    ss << '-';
            }
            return ss.str();
        }
    };

    class FileHelper
    {
    public:
        FileHelper(const std::string& name)
        : _filename(name)
        {}

        bool exists()
        {
            struct stat st;
            return stat(_filename.c_str(), &st) == 0;
        }

        size_t size()
        {
            struct stat st;
            int n = stat(_filename.c_str(), &st);
            if (n != 0) return 0;
            return st.st_size;
        }

        bool read(char* body, size_t offset, size_t len)
        {
            // 1.打开文件
            std::ifstream ifs(_filename, std::ios::binary | std::ios::in); // 防止数据出现损失，使用二进制形式打开
            if (ifs.is_open() == false)
            {
                ELOG("打开文件 %s 失败", _filename.c_str());
                return false;
            }
            // 2.跳转到指定位置
            ifs.seekg(offset, std::ios::beg);
            // 3.读取文件
            ifs.read(body, len);
            if (ifs.good() == false)
            {
                ELOG("读取文件 %s 失败", _filename.c_str());
                ifs.close(); // 不要忘了关闭文件
                return false;
            }
            // 4.关闭文件
            ifs.close();
            return true;
        }

        bool read(std::string& body)
        {
            // 读取整个文件
            size_t sz = this->size();
            body.resize(sz);
            return read(&body[0], 0, sz);
        }

        bool write(const char* body, size_t offset, size_t len)
        {
            // 1.打开文件
            // ofstream 没有读取权限，因此不能跳转。所以使用 fstream
            std::fstream fs(_filename, std::ios::binary | std::ios::out | std::ios::in);
            if (fs.is_open() == false)
            {
                ELOG("打开文件 %s 失败", _filename.c_str());
                return false;
            }
            // 2.跳转到指定位置
            fs.seekg(offset, std::ios::beg);
            // 3.写入文件
            fs.write(body, len);
            if (fs.good() == false)
            {
                ELOG("写入文件 %s 失败", _filename.c_str());
                fs.close();
                return false;
            }
            // 4.关闭文件
            fs.close();
            return true;
        }

        bool write(std::string& body)
        {
            return write(body.c_str(), 0, body.size());
        }

        bool rename(const std::string& newname)
        {
            return ::rename(_filename.c_str(), newname.c_str()) == 0;
        }

        static std::string parentDirectory(const std::string& filename)
        {
            // /aa/bb/test.cc   test.cc
            size_t pos = filename.find_last_of('/');
            if (pos == std::string::npos)
            {
                // 没有上级目录
                return "./";
            }
            return filename.substr(0, pos);
        }

        static bool createFile(const std::string& filename)
        {
            // 1.以写的方式打开文件，文件不存在就会创建
            std::fstream fs(filename, std::ios::binary | std::ios::out);
            if (fs.is_open() == false)
            {
                ELOG("打开文件 %s 失败", filename.c_str());
                return false;
            }
            // 2.关闭文件
            fs.close();
            return true;
        }

        static bool removeFile(const std::string& filename)
        {
            return ::remove(filename.c_str()) == 0;
        }

        static bool createDirectory(const std::string& path)
        {
            // /aaa/bbb/ccc     cccc
            int pos = 0, idx = 0;
            while(idx < path.size())
            {
                pos = path.find("/", idx);
                if (pos == std::string::npos)
                {
                    // 最后一级目录或者只有一级目录
                    return ::mkdir(path.c_str(), 0775) == 0;
                }

                std::string subpath = path.substr(0, pos);
                int n = ::mkdir(subpath.c_str(), 0775);
                if (n != 0 && errno != EEXIST)
                {
                    ELOG("创建目录 %s 失败：%s", path.c_str(), strerror(errno));
                    return false;
                }
                idx = pos + 1;
            }
            return true;
        }

        static bool removeDirectory(const std::string& path)
        {
            std::string cmd = "rm -rf " + path;
            return ::system(cmd.c_str()) != -1;
        }
    private:
        std::string _filename;
    };
}

#endif